package com.psychologyCommunity.Community.Contoller.interceptor;

import com.psychologyCommunity.Community.entity.LoginTicket;
import com.psychologyCommunity.Community.entity.User;
import com.psychologyCommunity.Community.service.UserService;
import com.psychologyCommunity.Community.util.CookieUtil;
import com.psychologyCommunity.Community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
    //在请求的一开始 就去获取Ticket 从而利用Ticket去查找有没有对应的User  如果有的话就暂存一下
    //为什么要在 最开始做这个事儿，因为在整个请求的过程当中可能随时随地都要用到当前用户
    //preHandle方法是由接口定义的，它的参数可不能随便去加，因此我们就不能用@CookieValue 注解来直接获得cookie中的值
    //但是给了传回来的 request ,cookie是通过request传回来的，所以能通过request中取得cookie，有点麻烦 所以，将其封装CookieUtil 方便之后复用

    //把这个UserService 注入 方便拿里面的方法
    @Autowired
    private UserService userService;
    @Autowired
    private HostHolder hostHolder;
    //preHandler做的事：在请求开始之初，我通过凭证找到用户，暂存到了hostHolder（自己定义的一个工具类，还是利用了ThreadLocal）中
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      //step1--获取cookie ，从cookie中获取凭证
        String ticket = CookieUtil.getValue(request, "ticket");
        //如果ticket有值 就是用户已经登陆了 --用这个ticket去查询一下用户的值--在UserService中写了这个方法
        if(ticket!=null){ //就把登录凭证表 中 的这条数据拿到
            LoginTicket loginTicket = userService.findLoginTicket(ticket);//(查询凭证)
            //凭证 要检查一下 是否还有效 ==查到了数据 并且状态是0（有效的）并且有一个有效时间 晚于当前时间
            if(loginTicket !=null && loginTicket.getStatus()==0 && loginTicket.getExpired().after(new Date())){
                //有效的话就 根据凭证查询 用户
                User user = userService.findUserById(loginTicket.getUserId());
                //在本次请求中持有用户，-- 因浏览器对服务器 是一对多的情况 ，所以要
                // 考虑多线程的情况--》如果我们单纯存在一个变量里，在并发情况下很可能产生冲突，有一个ThreadLocal类 来解决这个冲突问题//ThreadLocal为变量在每个线程中都创建了一个副本，那么每个线程可以访问自己内部的副本变量.
                //为什么我们现在能持有 ，因为我们把数据存入了 当前线程对应的map中
                // --这个请求没有处理完，这个现在就还在，当请求处理完，服务器向浏览器做出响应之后，线程销毁；所以这个请求的过程中，线程一直存在，ThreadLocal中的数据也是一直存在的
                hostHolder.setUserThreadLocalInfo(user);
                //-Security的应用
                //构建用户认证的结果，并存入到SecurityContext,以便于Security进行授权
                //拿到用户的权限
               Authentication usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        user, user.getPassword(), userService.getAuthorities(user.getId()));
                //存到context中
                SecurityContextHolder.setContext(new SecurityContextImpl(usernamePasswordAuthenticationToken));

            }
        }
        return true; }
      // 这个user用的时机就是 ，因为模板引擎要用，所以在模板引擎调用之前把这个user存在model中
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //先从hostHolder中拿到这个user
        User user = hostHolder.getUser();
        if(user!=null &&modelAndView!=null){
            modelAndView.addObject("loginUser",user);
        } }
    //在什么时候要把这个user清理掉？，在模板都执行完了就可以清理掉数据
//    @Override
//    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        hostHolder.clear();//请求结束之后  hostHolder做了一个清理
//        SecurityContextHolder.clearContext();//保存权限的逻辑也可以清理掉
//    }
}
